using Jint.Native;

namespace Jint.Runtime.Interpreter.Expressions;

internal sealed class JintNewExpression : JintExpression
{
    private JintExpression _calleeExpression = null!;
    private JintExpression[] _jintArguments = Array.Empty<JintExpression>();
    private bool _hasSpreads;
    private bool _initialized;

    public JintNewExpression(NewExpression expression) : base(expression)
    {
    }

    private void Initialize()
    {
        var expression = (NewExpression) _expression;
        _calleeExpression = Build(expression.Callee);

        if (expression.Arguments.Count <= 0)
        {
            return;
        }

        _jintArguments = new JintExpression[expression.Arguments.Count];
        for (var i = 0; i < _jintArguments.Length; i++)
        {
            var argument = expression.Arguments[i];
            _jintArguments[i] = Build(argument);
            _hasSpreads |= argument.Type == NodeType.SpreadElement;
        }
    }

    protected override object EvaluateInternal(EvaluationContext context)
    {
        if (!_initialized)
        {
            Initialize();
            _initialized = true;
        }

        var engine = context.Engine;

        // todo: optimize by defining a common abstract class or interface
        var jsValue = _calleeExpression.GetValue(context);

        JsValue[] arguments;
        if (_jintArguments.Length == 0)
        {
            arguments = Array.Empty<JsValue>();
        }
        else if (_hasSpreads)
        {
            arguments = BuildArgumentsWithSpreads(context, _jintArguments);
        }
        else
        {
            arguments = engine._jsValueArrayPool.RentArray(_jintArguments.Length);
            BuildArguments(context, _jintArguments, arguments);
        }

        // Reset the location to the "new" keyword so that if an Error object is
        // constructed below, the stack trace will capture the correct location.
        context.LastSyntaxElement = _expression;

        if (!jsValue.IsConstructor)
        {
            ExceptionHelper.ThrowTypeError(engine.Realm, _calleeExpression.SourceText + " is not a constructor");
        }

        // construct the new instance using the Function's constructor method
        var instance = engine.Construct(jsValue, arguments, jsValue, _calleeExpression);

        engine._jsValueArrayPool.ReturnArray(arguments);

        return instance;
    }
}